package jmine.selenium.services.registry;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.services.api.ActionsEnum;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.factory.ServiceCreationException;
import jmine.tec.utils.Tuple;

/**
 * Classe base de registros de serviço.
 * 
 * @author Rafael Volpato
 * @param <R> Tipo do objeto que registrado
 * @param <C> Tipo do objeto que é armazenado no cache.
 */
public abstract class AbstractServicesRegistry<R, C> implements ServicesRegistry<R> {

    private final Map<String, C> definitionCache = new ConcurrentHashMap<String, C>();

    /**
     * Cria um serviço a partir dos parametros informados.
     * 
     * @param action {@link ActionsEnum}
     * @param serviceName nome do serviço
     * @return uma instancia do serviço ou <code>null</code> se o serviço não foi registrado.
     * @throws ServiceCreationException se ocorrer algum erro
     */
    public final Service createService(ActionsEnum action, String serviceName) throws ServiceCreationException {
        String key = this.cacheKeyForService(action, serviceName);

        if (this.definitionCache.containsKey(key)) {
            return this.doCreateService(action, serviceName, this.definitionCache.get(key));
        }

        return null;
    }

    /**
     * Executa o registro dos parametros informados.
     * 
     * @param registers registers
     */
    public final void register(Collection<? extends R> registers) {
        if (registers != null && registers.size() > 0) {
            for (R registree : registers) {
                Tuple<String, C> cacheEntry = this.getEntryToRegister(registree);
                if (this.definitionCache.put(cacheEntry.head(), cacheEntry.tail()) != null) {
                    throw new ExceptionInInitializerError("services.api.factory.instantiationError");
                }
            }
        }
    }

    /**
     * Retorna a chave que deve ser utilizada para armazenar o serviço informado no cache.<br>
     * Formato: Action|ServiceName
     * 
     * @param action action
     * @param serviceName serviceName
     * @return a chave que deve ser utilizada para armazenar o serviço informado no cache
     */
    protected String cacheKeyForService(ActionsEnum action, String serviceName) {
        return action.name() + "|" + serviceName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        return this.getClass().equals(obj.getClass());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final int hashCode() {
        return this.getClass().hashCode();
    }

    /**
     * Cria uma nova instancia de {@link Service} a partir da definição informada.
     * 
     * @param action action
     * @param serviceName serviceName
     * @param serviceDefinition serviceDefinition
     * @return uma instancia do serviço
     * @throws ServiceCreationException se ocorrer algum erro
     */
    protected abstract Service doCreateService(ActionsEnum action, String serviceName, C serviceDefinition) throws ServiceCreationException;

    /**
     * Converte o objeto que esta sendo registrado no objeto que vai ser armazenado no registro de serviços, junto com sua chave.
     * 
     * @param registree objeto que esta sendo registrado
     * @return {@link Tuple} contendo a chave no registro e o objeto que vai ser armazenado
     */
    protected abstract Tuple<String, C> getEntryToRegister(R registree);
}
